/*
 * File:    HttpMimeTypes.java
 * Created: 27-Dec-2006
 * Version: $Id$
 *
 * COPYRIGHT (C) 2006, Bitgate Software, LLC.  All Rights Reserved.
 * RELEASED UNDER THE CREATIVE COMMONS LICENSE.
 *
 * kenji.hollis@bitgatesoftware.com
 */

package com.bitgate.util.service.protocol.http;

import static com.bitgate.util.debug.Debug.*;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;

import com.bitgate.util.config.Config;

/**
 * This class handles the storage and retrieval of mime types for Http requests, or file requests that need to have the
 * content type reported based on the filename.  The singleton instance of this class loads in a "mime.types" file from
 * the configuration directory, and parses it, storing the mime types - by file extension - in a hash map.  Once
 * loaded, that information is used when parsing the filename extensions, and used for a quick lookup.  It uses a
 * <code>ConcurrentHashMap</code> so the lookups are thread-safe.
 *
 * @author Kenji Hollis &lt;kenji@bitgatesoftware.com&gt;
 * @version $Id$
 */
public class HttpMimeTypes
{
    private static final HttpMimeTypes _default = new HttpMimeTypes();
    private final HashMap<String, String> httpMimeTypes;
    private final HashMap<String, String> scriptingMimeTypes;
    private final String unknownMimeType;
    
    private static final String MIME_TYPE_UNKNOWN = "unknown/unknown";

    /**
     * Constructor.
     */
    private HttpMimeTypes()
    {
    	this.httpMimeTypes = new HashMap<String, String>();
    	this.scriptingMimeTypes = new HashMap<String, String>();

		String mimeFilename = System.getProperty("mime.config");
	
		if (mimeFilename == null) {
		    mimeFilename = "conf/defaults/mime.xml";
		}
	
		Config.getDefault().register(mimeFilename, "mime");
	
		String mimeTypeFilename = Config.getDefault().parseConfig("mime", "/mime/object[@type='tuning']/property[@name='" +
		    "mime.filename']/@value");
	
		String mimeUnknownType = Config.getDefault().parseConfig("mime", "/mime/object[@type='tuning']/property[@name='" +
		    "mime.unknown']/@value");
	
		if (mimeTypeFilename != null) {
		    loadMimeTypes(mimeTypeFilename);
		} else {
		    warning("Mime type filename not specified in mime.xml file.");
		}
	
		if (mimeUnknownType != null) {
		    this.unknownMimeType = mimeUnknownType;
		} else {
		    this.unknownMimeType = MIME_TYPE_UNKNOWN;
		}
	
		if (isDebugEnabled()) {
			debug("Mime type filename = '" + mimeTypeFilename + "' Unknown Mimetype = '" + this.unknownMimeType + "'");
		}
	
		parseScriptingTypes();
    }

    private void parseScriptingTypes()
    {
		int numScripts = Integer.parseInt(Config.getDefault().parseConfig("mime", "count(/mime/object[@type='scripting'])"));
	
		for(int i = 1; i < (numScripts + 1); i++) {
		    String scrEngine = Config.getDefault().parseConfig("mime", "/mime/object[@type='scripting'][" + i + "]/" +
				"property[@name='scripting.engine']/@value");
		    String scrExtension = Config.getDefault().parseConfig("mime", "/mime/object[@type='scripting'][" + i + "]/" +
				"property[@name='scripting.file-extension']/@value");
		
		    if (isDebugEnabled()) {
		    	debug("Registered file extension '" + scrExtension + "' with engine '" + scrEngine + "'");
		    }
		    
		    this.scriptingMimeTypes.put(scrExtension, scrEngine);
		}
    }

    /**
     * Returns the default instance of the class.
     *
     * @return <code>static HttpMimeTypes</code> containing the default singleton instance.
     */
    public static HttpMimeTypes getDefault()
    {
    	return _default;
    }

    /**
     * Parses the mime.types file specified.  It does not clear out previously loaded mime type entries: if you load in a new
     * one that overwrites a previously defined mime type, that mime type will be replaced with the contents of the new
     * file.
     *
     * @param filename The filename to parse.
     */
    public void loadMimeTypes(String filename)
    {
		BufferedReader in = null;
		String str = null;
		int numMimeEntries = 0;
	
		try {
		    in = new BufferedReader(new FileReader(filename));
		} catch(FileNotFoundException e) {
		    warning("Unable to load mime types filename '" + filename + "': " + e.getMessage());
		    return;
		}
	
		try {
		    while((str = in.readLine()) != null) {
				if ("".equals(str) || str.startsWith("#")) {
				    continue;
				}
		
				String[] mimeEntries = str.split("\t");
		
				if (mimeEntries.length > 1) {
				    String[] mimeExtensions = mimeEntries[(mimeEntries.length - 1)].split(" ");
		
				    for(int i = 0; i < mimeExtensions.length; i++) {
						if (mimeExtensions[i].equals("")) {
						    continue;
						}
			
						addMimeEntry(mimeExtensions[i], mimeEntries[0]);
						numMimeEntries++;
				    }
		
				    mimeExtensions = null;
				}
		
				mimeEntries = null;
		    }
		} catch(IOException e) {
			if (isDebugEnabled()) {
				debug("Unable to load mime types from file '" + filename + "': " + e.getMessage());
			}
		}
	
		try {
		    in.close();
		} catch(IOException e) {
			if (isDebugEnabled()) {
				debug("Unable to close filename '" + filename + "': " + e.getMessage());
			}
		}
	
		if (isDebugEnabled()) {
			debug("Mime types processed count = " + numMimeEntries);
		}
    }

    /**
     * Allows a mechanism to add a mime type entry based on the file extension.
     *
     * @param extension The file extension to add; does not need a "." in the extension.
     * @param mimeType The mime type to return for this extension.
     */
    public void addMimeEntry(String extension, String mimeType)
    {
		this.httpMimeTypes.put(extension, mimeType);
		
		if (isDebugEnabled()) {
			debug("Added extension='" + extension + "' type='" + mimeType + "'");
		}
    }

    /**
     * Parses a filename for the mime type specified.
     *
     * @param filename The filename to be parsed.
     * @return <code>String</code> containing the mime type found.  Returns "unknown/unknown" if none was matched.
     */
    public String getMimeType(String filename)
    {
		if (filename.lastIndexOf(".") != -1) {
		    String fileExtension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
		    String foundMime = this.httpMimeTypes.get(fileExtension);
	
		    if (isDebugEnabled()) {
		    	debug("File extension='" + fileExtension + "' mime='" + foundMime + "'");
		    }
	
		    if (foundMime != null) {
		    	return foundMime;
		    }
	
		    return this.unknownMimeType;
		}
	
		if (isDebugEnabled()) {
			debug("Filename '" + filename + "' does not contain a period, so filename extension is non-existent.");
		}
		
		return this.unknownMimeType;
    }

    /**
     * Determines whether or not a filename is a scriptable extension.
     *
     * @param filename The filename to be parsed.
     * @return <code>String</code> containing the scripting engine found.  Returns <code>null</code> otherwise.
     */
    public String getScriptingEngine(String filename)
    {
		if (filename.lastIndexOf(".") != -1) {
		    String fileExtension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
		    String foundMime = this.scriptingMimeTypes.get(fileExtension);
	
		    if (isDebugEnabled()) {
		    	debug("File extension='" + fileExtension + "' scriptingEngine='" + foundMime + "'");
		    }
	
		    if (foundMime != null) {
		    	return foundMime;
		    }
	
		    return null;
		}
	
		if (isDebugEnabled()) {
			debug("Filename '" + filename + "' does not contain a period, so filename extension is non-existent.");
		}
		
		return null;
    }
}
